package pta;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.lang.model.util.ElementScanner6;

import javafx.util.Pair;

import java.util.TreeMap;
import java.util.TreeSet;
import java.util.ArrayList;

import soot.Local;
import soot.MethodOrMethodContext;
import soot.Scene;
import soot.SceneTransformer;
import soot.SootMethod;
import soot.Unit;
import soot.Value;
import soot.baf.Inst;
import soot.jimple.DefinitionStmt;
import soot.jimple.IntConstant;
import soot.jimple.InvokeExpr;
import soot.jimple.InvokeStmt;
import soot.jimple.NewExpr;
import soot.jimple.ParameterRef;
import soot.jimple.SpecialInvokeExpr;
import soot.jimple.ThisRef;
import soot.jimple.InstanceFieldRef;
import soot.jimple.toolkits.callgraph.ReachableMethods;
import soot.toolkits.scalar.ForwardFlowAnalysis;
import soot.util.queue.QueueReader;
import soot.SootField;
import java.util.HashMap;

// class Var extends Pair<Local, SootField> {
// 	public Var() {
// 		super(null, null);
// 	}

// 	public Var(Local v) {
// 		super(v, null);
// 	}

// 	public Var(InstanceFieldRef v) {
// 		super((Local) (v.getBase()), v.getField());
// 	}

// 	@Override
// 	public String toString() {
// 		if (this.getValue() == null)
// 			return this.getKey().toString();
// 		else
// 			return "(" + this.getKey().toString() + "," + this.getValue().toString() + ")";
// 	}
// }
public class WholeProgramTransformer extends SceneTransformer {

	@Override
	protected void internalTransform(String arg0, Map<String, String> arg1) {

		TreeMap<Integer, Local> queries = new TreeMap<Integer, Local>();
		HashMap<Pair<SootMethod, Integer>, List<Local>> valOfArg = new HashMap<Pair<SootMethod, Integer>, List<Local>>();

		Anderson anderson = new Anderson();

		ReachableMethods reachableMethods = Scene.v().getReachableMethods();
		QueueReader<MethodOrMethodContext> qr = reachableMethods.listener();

		// while (qr.hasNext()) {
		// SootMethod sm = qr.next().method();
		// if (sm.toString().contains("Ivk")) {
		// System.out.println(sm);
		// if (sm.hasActiveBody()) {
		// for (Unit u : sm.getActiveBody().getUnits()) {
		// System.out.println("S: " + u);
		// System.out.println(u.getClass());
		// if (u instanceof DefinitionStmt) {

		// Value lOp = ((DefinitionStmt) u).getLeftOp(), rOp = ((DefinitionStmt)
		// u).getRightOp();
		// System.out.println("lOp is a object of " + lOp.getClass());
		// System.out.println("rOp is a object of" + rOp.getClass());
		// System.out.println("Type of lOp is " + lOp.getType());
		// System.out.println("Type of rOp is " + rOp.getType());
		// System.out.println("lOp is :" + u.getDefBoxes().get(0));
		// System.out.println("rOp is :" + u.getUseBoxes().get(0));
		// }
		// System.out.println();
		// }
		// System.out.println("---------------------------");
		// }
		// }
		// }
		TreeSet<Integer> all = new TreeSet<Integer>();
		while (qr.hasNext()) {
			SootMethod sm = qr.next().method();
			if (sm.isJavaLibraryMethod()) {
				continue;
			}
			// if (sm.toString().contains("FieldSensitivity")) // intra-procedure (only
			// analysis
			// {
			// System.out.println(sm);
			int allocId = 0;

			if (sm.hasActiveBody()) {
				for (Unit u : sm.getActiveBody().getUnits()) {
					// System.out.println("S: " + u);
					// System.out.println(u.getClass());
					if (u instanceof InvokeStmt) {
						InvokeExpr ie = ((InvokeStmt) u).getInvokeExpr();
						if (ie.getMethod().toString().equals("<benchmark.internal.BenchmarkN: void alloc(int)>")) {
							allocId = ((IntConstant) ie.getArgs().get(0)).value;
							all.add(allocId);
						} else if (ie.getMethod().toString()
								.equals("<benchmark.internal.BenchmarkN: void test(int,java.lang.Object)>")) {
							Value v = ie.getArgs().get(1);
							int id = ((IntConstant) ie.getArgs().get(0)).value;
							if (v instanceof Local)
								queries.put(id, (Local) v);
						} else {

							SootMethod m = ie.getMethod();

							// System.out.println("ie: " + ie + " class:" + );
							for (Integer i = 0; i < m.getParameterCount(); ++i)
								if (ie.getArg(i) instanceof Local) {
									Pair<SootMethod, Integer> argi = new Pair<SootMethod, Integer>(m, i);
									if (!valOfArg.containsKey(argi))
										valOfArg.put(argi, new ArrayList<Local>());
									valOfArg.get(argi).add((Local) ie.getArg(i));
								}

							if (ie instanceof SpecialInvokeExpr) {
								Value v = ((SpecialInvokeExpr) ie).getBase();
								if (v instanceof Local) {
									Pair<SootMethod, Integer> argi = new Pair<SootMethod, Integer>(m, -1);
									if (!valOfArg.containsKey(argi))
										valOfArg.put(argi, new ArrayList<Local>());
									valOfArg.get(argi).add((Local) v);
								}
							}
						}

					}

					if (u instanceof DefinitionStmt) {

						Value lOp = ((DefinitionStmt) u).getLeftOp(), rOp = ((DefinitionStmt) u).getRightOp();
						// System.out.println("lOp is a object of " + lOp.getClass());
						// System.out.println("rOp is a object of" + rOp.getClass());
						// System.out.println("Type of lOp is " + lOp.getType());
						// System.out.println("Type of rOp is " + rOp.getType());
						// System.out.println("lOp is :" + u.getDefBoxes().get(0));
						// System.out.println("rOp is :" + u.getUseBoxes().get(0));
						if (rOp instanceof NewExpr) {
							// System.out.println("Alloc " + allocId);
							if (allocId > 0)
								anderson.addNewConstraint(allocId, (Local) lOp);
						}
						if (lOp instanceof Local && rOp instanceof Local) {
							Local l = (Local) lOp, r = (Local) rOp;
							anderson.addAssignConstraint(r, l);
						}
						if (lOp instanceof Local && rOp instanceof InstanceFieldRef) {
							Local l = (Local) lOp;
							InstanceFieldRef r = (InstanceFieldRef) rOp;
							anderson.addFieldLoadConstraint((Local) r.getBase(), r.getField(), l);
						}
						if (lOp instanceof InstanceFieldRef && rOp instanceof Local) {
							InstanceFieldRef l = (InstanceFieldRef) lOp;
							Local r = (Local) rOp;
							anderson.addFieldStoreConstraint(r, (Local) l.getBase(), l.getField());
						}
						if (lOp instanceof Local && rOp instanceof ParameterRef) {
							Local l = (Local) lOp;
							ParameterRef r = (ParameterRef) rOp;
							Pair<SootMethod, Integer> argi = new Pair<SootMethod, Integer>(sm, r.getIndex());
							if (valOfArg.get(argi) != null)
								for (Local local : valOfArg.get(argi)) {
									anderson.addAssignConstraint(local, l);
								}
						}
						if (lOp instanceof Local && rOp instanceof ThisRef) {
							Local l = (Local) lOp;
							ThisRef r = (ThisRef) rOp;
							Pair<SootMethod, Integer> argi = new Pair<SootMethod, Integer>(sm, -1);
							if (valOfArg.get(argi) != null)
								for (Local local : valOfArg.get(argi)) {
									anderson.addAssignConstraint(local, l);
								}
						}

					}
					// System.out.println();
				}

			}
			// System.out.println("-----------");
			// }
		}
		String answer = "";
		try {
			anderson.run();

			for (Entry<Integer, Local> q : queries.entrySet()) {
				TreeSet<Integer> result = anderson.getPointsToSet(q.getValue());
				answer += q.getKey().toString() + ":";

				for (Integer i : result) {
					answer += " " + i;
				}
				answer += "\n";
			}
		} catch (Exception e) {

			for (Entry<Integer, Local> q : queries.entrySet()) {
				answer += q.getKey().toString() + ":";
				for (Integer i : all) {
					answer += " " + i;
				}
				answer += "\n";
			}
		}

		AnswerPrinter.printAnswer(answer);

	}

}
